﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Search;
using Windows.Storage.Streams;
using BlogApp.Data.Models;
using HtmlAgilityPack;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Buffer = System.Buffer;

namespace BlogApp.Data.Service
{
    public class DataService : DataServiceBase
    {
        StorageFolder _localStorage = ApplicationData.Current.LocalFolder;
        private JsonSerializerSettings _serializerSettings;

        private static string BASIC_URL = null;

        private string _urlRecentPost = null;
        private string _urlGetAllPost = null;
        private string _urlGetCategories = null;
        private string _urlGetPostsBySlug = null;
        private string _urlSearchPost = null;
        private string _urlCommentPost = null;


        private static DataService _instance;
        public static DataService Instance
        {
            get
            {
                if (_instance != null)
                {
                    return _instance;
                }

                _instance = new DataService();

                return _instance;
            }
        }


        private DataService() { }

        public async void InitDataservice(string blogUrl)
        {
            BASIC_URL = blogUrl;

            _urlRecentPost = string.Format(BASIC_URL, "?json=*get_recent_posts&page={0}&count={1}");
            _urlGetAllPost = string.Format(BASIC_URL, "?json=get_recent_post");
            _urlGetCategories = string.Format(BASIC_URL, "?json=get_category_index");
            _urlGetPostsBySlug = string.Format(BASIC_URL, "?json=get_category_posts&page={0}&count={1}&id={2}");
            _urlSearchPost = string.Format(BASIC_URL, "?json=get_search_results&page={0}&count={1}&search={2}");
            _urlCommentPost = string.Format(BASIC_URL, "?json=submit_comment&post_id={0}&name={1}&email={2}&content={3}");


            _serializerSettings = new JsonSerializerSettings()
                                  {
                                      NullValueHandling = NullValueHandling.Ignore,
                                      Error = OnSerializerError,
                                      MissingMemberHandling = MissingMemberHandling.Ignore
                                  };
        }

        private void OnSerializerError(object sender, ErrorEventArgs e)
        {
            Debug.WriteLine("{0} - {1}", e.CurrentObject, e.ErrorContext);
        }

        private async Task<T> parseResult<T>(string js)
        {
            try
            {
                var result = await JsonConvert.DeserializeObjectAsync<T>(js, _serializerSettings);
                return result;
            }
            catch (Exception ex)
            {
                Debug.WriteLine("error parsing result: " + ex.Message);
                return default(T);
            }
        }


        public async Task<PostResult> GetAllPostsAsync()
        {
            var url = _urlGetAllPost;
            PostResult result = null;
            var js = await GetAsync(url);

            if (!String.IsNullOrEmpty(js))
            {
                result = await parseResult<PostResult>(js);
                if (result == null)
                {

                }
            }

            if (result != null)
            {

            }

            return result;
        }

        public async Task<PostResult> GetRecentPostsAsync(int page = 1, int count = 20)
        {
            var url = string.Format(_urlRecentPost, page, count);
            PostResult result = null;
            var js = await GetAsync(url);

            if (!String.IsNullOrEmpty(js))
            {
                result = await parseResult<PostResult>(js);
                if (result == null)
                {

                }
            }

            if (result != null)
            {

            }

            return result;
        }

        public async Task<PostResult> GetPostsBySlugAsync(Category category, int page = 1)
        {
            var url = string.Format(_urlGetPostsBySlug, page, Statics.POST_COUNT, category.Id);
            PostResult result = null;
            var js = await GetAsync(url);

            if (!String.IsNullOrEmpty(js))
            {
                result = await parseResult<PostResult>(js);
                if (result == null)
                {

                }
            }

            if (result != null)
            {

            }

            return result;
        }

        public async Task<PostResult> GetPostsByQueryAsync(string query, int page = 1)
        {
            var url = string.Format(_urlSearchPost, page, Statics.POST_COUNT, query);
            PostResult result = null;
            var js = await GetAsync(url);

            if (!String.IsNullOrEmpty(js))
            {
                result = await parseResult<PostResult>(js);
                if (result == null)
                {

                }
            }

            if (result != null)
            {

            }

            return result;
        }

        public async Task<CommentResponse> SubmitCommentAsync(string postId, string name, string email, string comment)
        {
            comment = comment.Replace("\r", "%0D%0A");
            var url = string.Format(_urlCommentPost, postId, name, email, comment);

            try
            {
                var js = await GetAsync(url);

                if (!String.IsNullOrEmpty(js))
                {
                    var  result = await parseResult<CommentResponse>(js);
                    if (result != null)
                    {
                        return result;
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }

            return null;
        }

        public async Task<CategoryResult> GetCategoriesAsync()
        {
            var url = _urlGetCategories;
            CategoryResult result = null;
            var js = await GetAsync(url);

            if (!String.IsNullOrEmpty(js))
            {
                result = await parseResult<CategoryResult>(js);
                if (result == null)
                {

                }
            }

            if (result != null)
            {

            }

            return result;
        }

        public async Task SaveOffline(Post blogpost)
        {
            blogpost.IsOffline = true;
            var offlinePostFolder = await _localStorage.CreateFolderAsync(blogpost.Id.ToString(), CreationCollisionOption.ReplaceExisting);

            var offlineContent = await handleOfflineImages(offlinePostFolder, blogpost.Content);
            blogpost.Content = offlineContent;


            var result = await JsonConvert.SerializeObjectAsync(blogpost);
            var content = stringToByteArray(result);

            var filename = string.Format("{0}.json", blogpost.Id);


            await writreFileToStorage(offlinePostFolder, filename, content);
        }

        public async Task RemoveOffline(Post blogpost)
        {
            var folder = await _localStorage.GetFolderAsync(blogpost.Id.ToString());
            await folder.DeleteAsync(StorageDeleteOption.Default);
        }



        private static async Task writreFileToStorage(StorageFolder offlinePostFolder, string filename, byte[] content)
        {
            try
            {
                var file = await offlinePostFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);

                using (var s = await file.OpenStreamForWriteAsync())
                {
                    await s.WriteAsync(content, 0, content.Length);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }

        public async Task<List<Post>> LoadOfflinePosts()
        {
            var offlinePosts = new List<Post>();

            //var queryOptions = new QueryOptions(CommonFileQuery.DefaultQuery, new[] { ".json" });
            //var offlineCacheFiles = _localStorage.CreateFileQueryWithOptions(queryOptions);
            //var files = await offlineCacheFiles.GetFilesAsync();

            var offlinePostFolders = await _localStorage.GetFoldersAsync();

            foreach (var offlinePostFolder in offlinePostFolders)
            {
                var files = await offlinePostFolder.GetFilesAsync();
                foreach (var storageFile in files.Where(e => e.Name.Contains(".json")))
                {
                    byte[] data;
                    using (var s = await storageFile.OpenStreamForReadAsync())
                    {
                        data = new byte[s.Length];
                        await s.ReadAsync(data, 0, (int)s.Length);
                    }

                    var content = byteArrayToString(data);
                    offlinePosts.Add(await parseResult<Post>(content));
                }
            }
            return offlinePosts;
        }

        private byte[] stringToByteArray(string str)
        {
            var bytes = new byte[str.Length * sizeof(char)];
            Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
            return bytes;
        }

        private string byteArrayToString(byte[] bytes)
        {
            var chars = new char[bytes.Length / sizeof(char)];
            Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
            return new string(chars);
        }

        private async Task<string> handleOfflineImages(StorageFolder offlinePostFolder, string content)
        {
            // download images and replace in html content
            var document = new HtmlDocument();
            document.LoadHtml(content);

            var imageCollection = document.DocumentNode.Descendants("img");
            foreach (var htmlNode in imageCollection)
            {
                var htmlImageAttribute = htmlNode.Attributes.FirstOrDefault(e => e.Name == "src");
                if (htmlImageAttribute != null)
                {
                    var imageUrl = htmlImageAttribute.Value;
                    var imageName = imageUrl.Split(new[] { '/' }).Last();
                    var filename = Guid.NewGuid().ToString();
                    
                    if (Statics.IMAGE_FILE_EXTENSIONS.Any(ext => imageName.EndsWith(ext)))
                    {
                        var splitFilename = imageName.Split(new[] { '.' }).Last();
                        filename += string.Format(".{0}",splitFilename);
                    }

                    Debug.WriteLine("DataService.cs | handleOfflineImages | New Filename: {0}", filename);
                    
                    Debug.WriteLine("download image {0}", imageUrl);
                    var data = await GetBinaryDataResponseAsync(imageUrl);
                    if (data.HasData)
                    {
                        try
                        {
                            await writreFileToStorage(offlinePostFolder, filename, data.Bytes);

                            htmlImageAttribute.Value = new Uri(string.Format("{0}/{1}", offlinePostFolder.Name, filename), UriKind.Relative).ToString();
                        }
                        catch (Exception exp)
                        {
                            Debug.WriteLine("handleOfflineImages | error: {0}", exp.Message);
                        }
                    }
                }
            }

            return document.DocumentNode.InnerHtml;
        }
    }
}